package cn.scnu.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.util.StrUtil;
import cn.scnu.common.model.Pagination;
import cn.scnu.common.model.PaginationData;
import cn.scnu.component.AsyncFun2;
import cn.scnu.component.exception.Asserts;
import cn.scnu.mapper.W3dServiceInfoMapper;
import cn.scnu.mapper.WBydataapplySwMapper;
import cn.scnu.model.W3dServiceInfoEntity;
import cn.scnu.model.dto.W3dServiceInfoDTO;
import cn.scnu.model.dto.W3dServiceInfoUpdateDTO;
import cn.scnu.model.query.W3dServiceInfoQueryParam;
import cn.scnu.service.IW3dServiceInfoService;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;

import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toCollection;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author hqzqaq
 * @since 2024-12-10
 */
@Service
@RequiredArgsConstructor
@DS("master_1")
@Slf4j
public class W3dServiceInfoServiceImpl extends ServiceImpl<W3dServiceInfoMapper, W3dServiceInfoEntity> implements IW3dServiceInfoService {

    private final W3dServiceInfoMapper mapper;

    private final AsyncFun2 asyncFun2;

    @Value("${file.download.dir}")
    private String downloadDir;

    @Value("${file.download.urlSuffix}")
    private String urlSuffix;

    private final WBydataapplySwMapper fromMapper;

    @Override
    public W3dServiceInfoDTO getW3dServiceInfoById(String id) {
        return po2dto(mapper.selectById(id));
    }

    @Override
    public PaginationData<W3dServiceInfoDTO> getW3dServiceInfoPage(W3dServiceInfoQueryParam queryParam) {
        LambdaQueryWrapper<W3dServiceInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.ge(queryParam.getStartPublishTime() != null, W3dServiceInfoEntity::getPublishTime, queryParam.getStartPublishTime())
                .le(queryParam.getEndPublishTime() != null, W3dServiceInfoEntity::getPublishTime, queryParam.getEndPublishTime())
                .like(queryParam.getDataName() != null, W3dServiceInfoEntity::getDataName, queryParam.getDataName())
                .eq(queryParam.getApprovalDataType() != null, W3dServiceInfoEntity::getApprovalDataType, queryParam.getApprovalDataType())
                .eq(queryParam.getAdministrativeRegion() != null, W3dServiceInfoEntity::getAdministrativeRegion, queryParam.getAdministrativeRegion())
                .eq(W3dServiceInfoEntity::getIsDirectory, false)
                .eq(StrUtil.isNotBlank(queryParam.getDataType()), W3dServiceInfoEntity::getDataType, queryParam.getDataType())
                .orderByDesc(W3dServiceInfoEntity::getPublishTime);

        QueryWrapper<W3dServiceInfoEntity> objectQueryWrapper = new QueryWrapper<>();
        objectQueryWrapper.select("distinct data_name")
                .lambda()
                .ge(queryParam.getStartPublishTime() != null, W3dServiceInfoEntity::getPublishTime, queryParam.getStartPublishTime())
                .le(queryParam.getEndPublishTime() != null, W3dServiceInfoEntity::getPublishTime, queryParam.getEndPublishTime())
                .like(queryParam.getDataName() != null, W3dServiceInfoEntity::getDataName, queryParam.getDataName())
                .eq(queryParam.getApprovalDataType() != null, W3dServiceInfoEntity::getApprovalDataType, queryParam.getApprovalDataType())
                .eq(queryParam.getAdministrativeRegion() != null, W3dServiceInfoEntity::getAdministrativeRegion, queryParam.getAdministrativeRegion())
                .eq(W3dServiceInfoEntity::getIsDirectory, false);

        Long count = mapper.selectCount(objectQueryWrapper);

        Page<W3dServiceInfoEntity> page = new Page<>(queryParam.getPageNo(), queryParam.getPageSize());
        page.setSearchCount(false);
        Page<W3dServiceInfoEntity> entityPage = mapper.selectPage(page, queryWrapper);

        List<W3dServiceInfoDTO> dtoList = entityPage.getRecords().stream()
                .map(this::po2dto)
                .collect(collectingAndThen(toCollection(() -> new TreeSet<>(Comparator.comparing(W3dServiceInfoDTO::getDataName))),
                        ArrayList::new));
        entityPage.setTotal(count);
        return new PaginationData<>(dtoList, new Pagination(entityPage));
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Integer insertW3dServiceInfo(W3dServiceInfoDTO w3dServiceInfoDTO) {
        String dataTypeParentId = "0";
        // 新增时补充目录，首先看有无数据类型的目录存在
        W3dServiceInfoEntity w3dServiceInfoEntity = mapper.selectOne(new LambdaQueryWrapper<W3dServiceInfoEntity>()
                .eq(W3dServiceInfoEntity::getDataName, w3dServiceInfoDTO.getDataType()));
        if (w3dServiceInfoEntity == null) {
            // 如果为空，则补充一条数据类型的目录
            w3dServiceInfoEntity = new W3dServiceInfoEntity();
            w3dServiceInfoEntity.setDataName(w3dServiceInfoDTO.getDataType());
            w3dServiceInfoEntity.setParentId(dataTypeParentId);
            w3dServiceInfoEntity.setIsDirectory(true);
            mapper.insert(w3dServiceInfoEntity);
        }
        String regionParentId = w3dServiceInfoEntity.getId();
        // 补充行政区划目录
        // 行政区划可能有多个，用逗号分割
        String administrativeRegion = w3dServiceInfoDTO.getAdministrativeRegion();
        for (String region : administrativeRegion.split(",")) {
            w3dServiceInfoEntity = mapper.selectOne(new LambdaQueryWrapper<W3dServiceInfoEntity>()
                    .eq(W3dServiceInfoEntity::getParentId, regionParentId)
                    .eq(W3dServiceInfoEntity::getDataName, region));
            if (w3dServiceInfoEntity == null) {
                w3dServiceInfoEntity = new W3dServiceInfoEntity();
                w3dServiceInfoEntity.setDataName(region);
                w3dServiceInfoEntity.setParentId(regionParentId);
                w3dServiceInfoEntity.setIsDirectory(true);
                mapper.insert(w3dServiceInfoEntity);
            }
            String publishTimeParentId = w3dServiceInfoEntity.getId();

            // 查询有没有这个项目
            w3dServiceInfoEntity = mapper.selectOne(new LambdaQueryWrapper<W3dServiceInfoEntity>()
                    .eq(W3dServiceInfoEntity::getParentId, publishTimeParentId)
                    .eq(W3dServiceInfoEntity::getDataName, w3dServiceInfoDTO.getDataName()));
            if (w3dServiceInfoEntity == null) {
                w3dServiceInfoEntity = new W3dServiceInfoEntity();
                w3dServiceInfoEntity.setDataName(w3dServiceInfoDTO.getDataName());
                w3dServiceInfoEntity.setParentId(publishTimeParentId);
                w3dServiceInfoEntity.setIsDirectory(true);
                mapper.insert(w3dServiceInfoEntity);
            }

            String publishRegionParentId = w3dServiceInfoEntity.getId();

            // 查询有没有时间数据项，提取发布时间，规范化为yyyy年MM月
            LocalDate localDate = w3dServiceInfoDTO.getPublishTime();
            Date date = Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant());
            String formattedDate = DateUtil.format(date, "yyyy年MM月");
            w3dServiceInfoEntity = mapper.selectOne(new LambdaQueryWrapper<W3dServiceInfoEntity>()
                    .eq(W3dServiceInfoEntity::getParentId, publishRegionParentId)
                    .eq(W3dServiceInfoEntity::getDataName, formattedDate));
            if (w3dServiceInfoEntity == null) {
                w3dServiceInfoEntity = dto2po(w3dServiceInfoDTO);
                w3dServiceInfoEntity.setDataName(formattedDate);
                w3dServiceInfoEntity.setParentId(publishRegionParentId);
                w3dServiceInfoEntity.setIsDirectory(Boolean.FALSE);
                mapper.insert(w3dServiceInfoEntity);
            } else {
                w3dServiceInfoEntity = dto2po(w3dServiceInfoDTO);
                Long dataCount = mapper.selectCount(new LambdaQueryWrapper<W3dServiceInfoEntity>()
                        .eq(W3dServiceInfoEntity::getParentId, publishRegionParentId)
                        .like(W3dServiceInfoEntity::getDataName, formattedDate));
                String dataName = formattedDate + "-(" + (dataCount + 1) + ")";
                w3dServiceInfoEntity.setDataName(dataName);
                w3dServiceInfoEntity.setParentId(publishRegionParentId);
                w3dServiceInfoEntity.setIsDirectory(Boolean.FALSE);
                mapper.insert(w3dServiceInfoEntity);
            }
        }
        return 1;
    }

    @Override
    public Integer updateW3dServiceInfo(W3dServiceInfoUpdateDTO w3dServiceInfoUpdateDTO) {
        W3dServiceInfoEntity w3dServiceInfoEntity = mapper.selectById(w3dServiceInfoUpdateDTO.getId());
        W3dServiceInfoDTO w3dServiceInfoDTO = BeanUtil.toBean(w3dServiceInfoUpdateDTO, W3dServiceInfoDTO.class);
        List<W3dServiceInfoEntity> entities = mapper.selectList(new LambdaQueryWrapper<W3dServiceInfoEntity>()
                .eq(W3dServiceInfoEntity::getDataName, w3dServiceInfoEntity.getDataName())
                .eq(W3dServiceInfoEntity::getDataType, w3dServiceInfoEntity.getDataType())
                .eq(W3dServiceInfoEntity::getApprovalDataType, w3dServiceInfoEntity.getApprovalDataType())
                .eq(W3dServiceInfoEntity::getAdministrativeRegion, w3dServiceInfoEntity.getAdministrativeRegion())
                .eq(W3dServiceInfoEntity::getPublishTime, w3dServiceInfoEntity.getPublishTime()));
        for (W3dServiceInfoEntity entity : entities) {
            w3dServiceInfoDTO.setId(entity.getId());
            w3dServiceInfoDTO.setParentId(entity.getParentId());
            mapper.updateById(dto2po(w3dServiceInfoDTO));
        }
        return entities.size();
    }

    @Override
    public Integer deleteW3dServiceInfoById(String id) {
        W3dServiceInfoEntity w3dServiceInfoEntity = mapper.selectById(id);
        if (w3dServiceInfoEntity.getIsDirectory()) {
            // 如果目录下没有子节点，则可以将目录进行删除，否则目录不可删除
            Long count = mapper.selectCount(new LambdaQueryWrapper<W3dServiceInfoEntity>().eq(W3dServiceInfoEntity::getParentId, id));
            if (count > 0) {
                Asserts.fail("目录下包含子数据，不可删除！");
                return 0;
            }
            mapper.deleteById(id);
        }

        List<String> ids = mapper.selectList(new LambdaQueryWrapper<W3dServiceInfoEntity>()
                        .eq(W3dServiceInfoEntity::getDataName, w3dServiceInfoEntity.getDataName())
                        .eq(W3dServiceInfoEntity::getDataType, w3dServiceInfoEntity.getDataType())
                        .eq(W3dServiceInfoEntity::getApprovalDataType, w3dServiceInfoEntity.getApprovalDataType())
                        .eq(W3dServiceInfoEntity::getAdministrativeRegion, w3dServiceInfoEntity.getAdministrativeRegion())
                        .eq(W3dServiceInfoEntity::getPublishTime, w3dServiceInfoEntity.getPublishTime()))
                .stream().map(W3dServiceInfoEntity::getId).collect(Collectors.toList());
        return mapper.deleteByIds(ids);
    }

    @Override
    public List<Tree<String>> getW3dServiceInfoCatalogue() {
        List<W3dServiceInfoDTO> w3dServiceInfoDTOS = mapper.selectList(new LambdaQueryWrapper<>()).stream().map(this::po2dto).collect(Collectors.toList());
        //配置
        TreeNodeConfig treeNodeConfig = new TreeNodeConfig();
        // 自定义属性名 都要默认值的
        treeNodeConfig.setIdKey("id");
        treeNodeConfig.setParentIdKey("parentId");
        return TreeUtil.build(w3dServiceInfoDTOS, findRootParentId(w3dServiceInfoDTOS), treeNodeConfig, (treeNode, tree) -> {
            tree.setId(treeNode.getId());
            tree.setParentId(treeNode.getParentId());
            tree.setName(treeNode.getDataName());
        });
    }

    @SneakyThrows
    @Override
    public String dataDownload(String sjmc, String da) {
        // 根据数据名称获取数据项，并根据数据名称去重
        LambdaQueryWrapper<W3dServiceInfoEntity> queryWrapper = new LambdaQueryWrapper<>();
        String[] split = sjmc.split(",");
        List<String> collect = Arrays.stream(split).collect(Collectors.toList());
        queryWrapper.in(W3dServiceInfoEntity::getDataName, collect);

        List<W3dServiceInfoEntity> w3dServiceInfoEntities = mapper.selectList(queryWrapper);

        if (w3dServiceInfoEntities.isEmpty()) {
            Asserts.fail("未找到数据项");
        }

        // 去重
        w3dServiceInfoEntities = w3dServiceInfoEntities.stream().collect(
                collectingAndThen(toCollection(() -> new TreeSet<>(Comparator.comparing(W3dServiceInfoEntity::getDataName))), ArrayList::new));

        String parentDirName = String.valueOf(System.currentTimeMillis());
        Path path = Paths.get(downloadDir, parentDirName);

        List<String> filePaths = w3dServiceInfoEntities.stream().map(W3dServiceInfoEntity::getDataStorageAddr).collect(Collectors.toList());
        asyncFun2.fileZipAndUpdateFormXzdzUrl(filePaths, path.toString(), da);

        String downloadUrl = "正在准备数据中，请稍后";
        fromMapper.updateSwzxdzById(da, downloadUrl);

        // File resZip = ZipUtil.zip(path.toString());
        // String downloadUrl = StrUtil.format("{}{}.zip", urlSuffix, parentDirName);

        // 返回压缩包下载地址
        // return downloadUrl;
        return downloadUrl;
    }

    @SneakyThrows
    @Override
    public void dataDownload(String fileName, HttpServletResponse response) {
        File file = new File(Paths.get(downloadDir, fileName).toString());
        if (!file.exists()) {
            Asserts.fail("文件不存在或未准备完毕");
        }
        // 文件下载 读到流中
        Path path = Paths.get(downloadDir);
        File targetFile = path.resolve(fileName).toFile();
        InputStream inputStream = new FileInputStream(targetFile);// 文件的存放路径
        response.reset();
        response.setContentType("application/octet-stream");
        response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8"));
        ServletOutputStream outputStream = response.getOutputStream();
        byte[] b = new byte[1024];
        int len;
        //从输入流中读取一定数量的字节，并将其存储在缓冲区字节数组中，读到末尾返回-1
        while ((len = inputStream.read(b)) > 0) {
            outputStream.write(b, 0, len);
        }
        inputStream.close();
    }

    private String findRootParentId(List<W3dServiceInfoDTO> nodeList) {
        for (W3dServiceInfoDTO node : nodeList) {
            String parentId = node.getParentId();
            boolean hasParent = false;

            for (W3dServiceInfoDTO otherNode : nodeList) {
                if (parentId.equals(otherNode.getId())) {
                    hasParent = true;
                    break;
                }
            }
            if (!hasParent) {
                // 如果没有找到父节点，则当前节点的父节点就是根目录的父节点
                return parentId;
            }
        }
        // 如果所有节点都有父节点，则返回一个默认值或者抛出异常，取决于实际情况
        return null;
    }

    private W3dServiceInfoDTO po2dto(W3dServiceInfoEntity entity) {
        return BeanUtil.toBean(entity, W3dServiceInfoDTO.class);
    }

    private W3dServiceInfoEntity dto2po(W3dServiceInfoDTO dto) {
        return BeanUtil.toBean(dto, W3dServiceInfoEntity.class);
    }
}
